package core;

import java.util.Arrays;

import util.Util;
import lpsolve.LpSolve;
import lpsolve.LpSolveException;

public class Optimizer {
	
	public static void calculateDea(Optimization optimi) throws CalculationException{
        long start = System.currentTimeMillis();
		try {

			LpSolve lp = constructDEALPModel(optimi);
			lp.setVerbose(LpSolve.IMPORTANT);
			lp.setScaling(1);
			lp.solve();
			//lp.writeLp("DEA_model_" + Util.getTimeStamp() +".lp");
			optimi.setOptimizationObjective(Util.truncate(lp.getObjective()));
			double[] variables = new double[lp.getNcolumns()];
			lp.getVariables(variables);
			optimi.setOptimizationWeights(Util.truncate(variables));
			optimi.setCalculated();
            optimi.setOptimizationTime(System.currentTimeMillis() - start);
		} catch (LpSolveException e) {
			e.printStackTrace();
		}
        //Util.print("DEA end " + Util.getTimeStamp());
	}
	public static void calculatePdm(PdmOptimization optimi) throws CalculationException{
        long start = System.currentTimeMillis();
		try {
			LpSolve lp = constructPdmLPModel(optimi);
			lp.setVerbose(LpSolve.IMPORTANT);
			lp.setScaling(1);
			lp.solve();
            //lp.writeLp("PDM_model_" + Util.getTimeStamp() +".lp");
			optimi.setOptimizationObjective(Util.truncate(lp.getObjective()));
			double[] variables = new double[lp.getNcolumns()];
			lp.getVariables(variables);
			optimi.setOptimizationWeights(Util.truncate(variables));			
			optimi.setCalculated();
            optimi.setOptimizationTime(System.currentTimeMillis() - start);
		} catch (LpSolveException e) {
			e.printStackTrace();
		}
        //Util.print("PDM end " + Util.getTimeStamp());
	}

	private static LpSolve constructPdmLPModel(PdmOptimization optimi) throws LpSolveException, CalculationException {	
		LpSolve lp;
		int j = 0;
		int Ncol = optimi.dmuList.inputSize + optimi.dmuList.outputSize;

		int[] colNo = new int[Ncol];
		double[] row = new double[Ncol];

		lp = LpSolve.makeLp(0, Ncol);
		if (lp.getLp() == 0) {
			Util.print("Could not generate new LP model");
			return null;
		}

		for (int i = 1; i <= Ncol; i++)
			lp.setColName(i, "u" + i);

		// add obj function
		j = 0;
		double tempVal = -1;
		for (int x = optimi.dmuList.inputSize; x < optimi.dmuList.inputSize + optimi.dmuList.outputSize; x++) {
			tempVal = optimi.dmuList.get(optimi.getObjectiveDmu()).criteria.get(x).amount - optimi.getAlpha()
					* optimi.getOptimizationDirection(x - optimi.dmuList.inputSize)
                    ;//* optimi.getRelUrmWeight(x - optimi.dmuList.inputSize);

			if (x - optimi.dmuList.inputSize == 0) {
				for (int cnt = 0; cnt < optimi.dmuList.outputSize; cnt++) {
					tempVal += optimi.getAlpha()
							* optimi.getOptimizationDirection(cnt)
							* optimi.getRelUrmWeight(cnt)
                            ;//* optimi.getRelUrmWeight(cnt);
				}
			}
			row[x-optimi.dmuList.inputSize+1] = tempVal;
		}

		lp.setObjFn(row);

		lp.setAddRowmode(true);

		// add denominator equality constraint
		j = 0;
		for (int x = 1; x <= optimi.dmuList.inputSize; x++) {
			colNo[j] = x + optimi.dmuList.outputSize;
			row[j++] = optimi.dmuList.get(optimi.getObjectiveDmu()).criteria.get(x - 1).amount;
		}
		lp.addConstraintex(j, row, colNo, LpSolve.EQ, 1);

		// add GE constraints
		for (int dmu = 0; dmu < optimi.dmuList.size(); dmu++) {

			j = 0;
			for (int x = optimi.dmuList.inputSize +1; x <= optimi.dmuList.inputSize + optimi.dmuList.outputSize; x++) {
				colNo[j] = x - optimi.dmuList.inputSize;
				row[j++] = optimi.dmuList.get(dmu).criteria.get(x - optimi.dmuList.inputSize).amount * -1;
			}
			for (int x = 1; x <= optimi.dmuList.inputSize; x++) {
				colNo[j] = x + optimi.dmuList.outputSize;
				row[j++] = optimi.dmuList.get(dmu).criteria.get(x - 1).amount;
			}

			lp.addConstraintex(j, row, colNo, LpSolve.GE, 0);
		}

		// add PDM constraints
		if(optimi.getAlpha() > 0){
			colNo = new int[Ncol];
			row = new double[Ncol];
	
			for (int x = 1; x < optimi.dmuList.outputSize; x++) {
				row = new double[Ncol];
					row[x + 1] = optimi.getAlpha()
							* optimi.getOptimizationDirection(x);
					row[1] = optimi.getAlpha()
							* optimi.getOptimizationDirection(x)
							* optimi.getRelUrmWeight(x) * -1;
				
					lp.addConstraint(row, LpSolve.GE, 0);
			}
		}

		// limit variation of variable values to 1/10
		for (int x = 1; x <= optimi.dmuList.outputSize; x++) {
			for (int z = 1; z <= optimi.dmuList.outputSize; z++) {
				if(z != x){
					row = new double[Ncol];
					row[x] = -0.1;
					row[z] = 1;
					lp.addConstraint(row, LpSolve.GE, 0);
				}
			}
		}

		lp.setAddRowmode(false);
		/* set the object direction to maximize */
		lp.setMaxim();

		return lp;

	}
	private static LpSolve constructDEALPModel(Optimization optimi) throws LpSolveException {
		LpSolve lp;
		int j = 0;
		int Ncol = optimi.dmuList.inputSize + optimi.dmuList.outputSize; 

		int[] colNo = new int[Ncol];
		double[] row = new double[Ncol];

		lp = LpSolve.makeLp(0, Ncol);

		if (lp.getLp() == 0) {
			Util.print("Could not generate new LP model");
			return null;
		}
		for (int i = 1; i <= Ncol; i++)
			lp.setColName(i, "u" + i);

		; /* makes building the model faster if it is done rows by row */

		// now add all constraints and the objective function

		// add obj function
		j = 0;
		for (int x = optimi.dmuList.inputSize; x <= optimi.dmuList.inputSize + optimi.dmuList.outputSize; x++) {
			colNo[j] = x -optimi.dmuList.inputSize;
			row[j++] = optimi.dmuList.get(optimi.getObjectiveDmu()).criteria.get(x - optimi.dmuList.inputSize).amount;
		}
		lp.setObjFnex(j, row, colNo);

		lp.setAddRowmode(true);

		// add denominator equality constraint
		j = 0;
		for (int x = 1; x <= optimi.dmuList.inputSize; x++) {
			colNo[j] = x + optimi.dmuList.outputSize;
			row[j++] = optimi.dmuList.get(optimi.getObjectiveDmu()).criteria.get(x - 1).amount;
		}
		lp.addConstraintex(j, row, colNo, LpSolve.EQ, 1);

		// add GE constraint
		for (int dmu = 0; dmu < optimi.dmuList.size(); dmu++) {

			j = 0;
			for (int x = optimi.dmuList.inputSize +1; x <= optimi.dmuList.inputSize + optimi.dmuList.outputSize; x++) {
				colNo[j] = x - optimi.dmuList.inputSize;
				row[j++] = optimi.dmuList.get(dmu).criteria.get(x - optimi.dmuList.inputSize).amount * -1;
			}
			for (int x = 1; x <= optimi.dmuList.inputSize; x++) {
				colNo[j] = x + optimi.dmuList.outputSize;
				row[j++] = optimi.dmuList.get(dmu).criteria.get(x - 1).amount;
			}

			lp.addConstraintex(j, row, colNo, LpSolve.GE, 0);
		}
		// limit variation of variable values to 1/10
		for (int x = 1; x <= optimi.dmuList.outputSize; x++) {
			for (int z = 1; z <= optimi.dmuList.outputSize; z++) {
				if(z != x){
					row = new double[Ncol];
					row[x] = -0.1;
					row[z] = 1;
					lp.addConstraint(row, LpSolve.GE, 0);
				}
			}
		}

		lp.setAddRowmode(false);
		/* set the object direction to maximize */
		lp.setMaxim();

		return lp;

	}
}
